package com.buer.dao.util.db;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 〈分页拦截器〉
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class }) })
public class PaginationInterceptor implements Interceptor {

    static int mappedStatementIndex = 0;

    static int parameterIndex = 1;

    static int rowBoundsIndex = 2;
    private Dialect dialect;
    /**
     * 日志工具
     */
    private static Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] queryArgs = invocation.getArgs();

        RowBounds rowBounds = (RowBounds) queryArgs[rowBoundsIndex];
        int offset = rowBounds.getOffset();
        int limit = rowBounds.getLimit();

        // 非分页查询
        if (offset == RowBounds.NO_ROW_OFFSET && limit == RowBounds.NO_ROW_LIMIT) {
            return invocation.proceed();
        }

        // 分页查询
        MappedStatement ms = (MappedStatement) queryArgs[mappedStatementIndex];
        Object parameter = queryArgs[parameterIndex];
        BoundSql boundSql = ms.getBoundSql(parameter);
        String sql = boundSql.getSql().trim();

        /*
         * 计算当前查询的总记录数，并根据分页条件抽取分页数据
         */
        if (offset == -1 && limit == -1) {
            StringBuilder pageCount = new StringBuilder(sql.length() + 100);
            if (sql.toLowerCase().indexOf("group") > -1 || sql.toLowerCase().indexOf("order") > -1) {
                pageCount.append("select count(*) as totalCount from (").append(sql).append(") as t");
            } else {
                sql = getCountSql(sql);
                pageCount.append("select count(*) as totalCount ").append(sql);
            }

            sql = pageCount.toString();
            queryArgs[rowBoundsIndex] = new RowBounds();
        } else {
            if (offset >= RowBounds.NO_ROW_OFFSET && limit != RowBounds.NO_ROW_LIMIT) {
                sql = dialect.getLimitString(sql, offset, limit);
            }
        }

        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(),
                boundSql.getParameterObject());
        // 解决foreach的分页参数丢失的bug
        MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
        List<ParameterMapping> pmLs = boundSql.getParameterMappings();
        for (ParameterMapping pm : pmLs) {
            String propertyName = pm.getProperty();
            // 这里只是增加以"_frec_"开始的参数，表示是for each参数，其他参数可以从ParameterObject获取。
            if (propertyName.startsWith("__frch_")) {
                newBoundSql.setAdditionalParameter(propertyName, boundSql.getAdditionalParameter(propertyName));
            }
        }
        queryArgs[rowBoundsIndex] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
        queryArgs[mappedStatementIndex] = newMs;

        return invocation.proceed();
    }

    /**
     * 功能描述: 分页sql 获取from之后的sql工具类<br>
     */
    public String getCountSql(String sql) {
        String sqlStr = replaceBlank(sql);
        sqlStr = sqlStr.replace(" from", " FROM");
        sqlStr = sqlStr.replace("from ", " FROM ");
        sqlStr = sqlStr.replace("select ", "SELECT ");
        MysqlPageDto mysqlPageDto = new MysqlPageDto();
        mysqlPageDto.setSql(sqlStr);
        while (mysqlPageDto.isFlag()) {
            checkFrom(mysqlPageDto);
        }

        return sql.substring(mysqlPageDto.getFromIndex());
    }

    public void checkFrom(MysqlPageDto mysqlPageDto) {
        String temp = mysqlPageDto.getSql().substring(mysqlPageDto.getSelectIndex() + 7, mysqlPageDto.getFromIndex());

        int selectTemp = temp.indexOf("SELECT");
        if (selectTemp > 0) {
            String sqltemp = mysqlPageDto.getSql();
            int nextSelect = sqltemp.indexOf("SELECT ", mysqlPageDto.getSelectIndex() + 7);
            mysqlPageDto.setSelectIndex(nextSelect);
            int fromIndexTemp = sqltemp.indexOf(" FROM ", mysqlPageDto.getFromIndex() + 5);
            if (fromIndexTemp >= 0) {
                mysqlPageDto.setFromIndex(fromIndexTemp);
            }
            mysqlPageDto.setFlag(true);
        } else {
            mysqlPageDto.setFlag(false);
        }
    }

    public String replaceBlank(String str) {
        String dest = "";
        if (str != null) {
            Pattern p = Pattern.compile("\\t|\r|\n");
            Matcher m = p.matcher(str);
            dest = m.replaceAll(" ");
        }
        return dest;
    }

    /**
     * 创建分页后新的MappedStatement
     */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {

        Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,
                ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        String[] in = ms.getKeyProperties();
        StringBuilder sb = new StringBuilder();
        if (in != null && in.length > 0) {
            for (int i = 0; i < in.length; i++) {
                sb.append(in[i]);
                if (i < in.length - 1) {
                    sb.append(",");
                }
            }
        }
        builder.keyProperty(sb.toString());
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.cache(ms.getCache());
        builder.resultSetType(ms.getResultSetType());
        return builder.build();
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        String dialectClass = properties.getProperty("dialectClass");
        try {
            dialect = (Dialect) Class.forName(dialectClass).newInstance();
        } catch (Exception e) {
            logger.error("获取或者加载实例化Dialect时发生异常", e);
        }
    }

    public static class BoundSqlSqlSource implements SqlSource {
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
